-module(gameboard).
-export([init/0, init/2]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Pasture Project - Advanced Functional Programming
%% Uppsala University - HT 11
%%
%% Module: gameboard.erl
%%
%% Author: Daniel Strebel, daniel.strebel@gmail.com
%% Author: Eduardo Hernandez, eduhrr@gmail.com
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Game initialization
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init() ->
    init(10, 10).

init(H, W) ->
    frame:init(),
    %setUpBoard
    frame ! {set_h, H},
    frame ! {set_w, W},
    F1 = init_horizontal_fence(0, W, 0, gb_trees:empty()),
    F2 = init_horizontal_fence(0, W, H-1, F1),
    F3 = init_vertical_fence(0, 1, H-2, F2),
    F4 = init_vertical_fence(W-1, 1, H-2, F3),
    G1 = init_cell(5, 5, grass, F4),
    G2 = init_cell(7, 6, grass, G1),
    G3 = init_cell(1, 1, grass, G2),
    F10 = init_cell(5, 4, fence, G3),
    F11 = init_cell(6, 4, fence, F10),
    R1 = init_cell(5, 6, rabbit, F11),
    R2 = init_cell(1, 2, rabbit, R1),
    FOX1 = init_cell(6, 6, fox, R2),
    FOX2 = init_cell(4, 6, fox, FOX1),
    io:fwrite("Welcome to PASTURE SIMULATOR~n", []),
    io:fwrite("~n", []),
    io:fwrite("############################~n", []),
    io:fwrite("#_V__v__V_v__V___V__V__V_V_#~n", []),
    io:fwrite("#_V_V_v__V___v__V__v_V_vV__#~n", []),
    io:fwrite("#_V__v__V_v__V___V__V__V_V_#~n", []),
    io:fwrite("#__V__V__V_V_v_v_V__V___Vv_#~n", []),
    io:fwrite("#_V_V_v__V___v__V__v_V_vV__#~n", []),
    io:fwrite("#_V__v__V_v__V___V__V__V_V_#~n", []),
    io:fwrite("############################~n", []),
    io:fwrite("~n", []),
    io:fwrite("To view the simulation please ~n", []),
    io:fwrite("use the following url in your browser:~n", []),
    io:fwrite("http://localhost:8088/:~n", []),
    boardLoop(gb_sets:empty(), FOX2, 0),
    ok.

init_horizontal_fence(_, 0, _, Tree) ->
    Tree;
init_horizontal_fence(X, W, Y, Tree) ->
    New_Tree = init_cell(X, Y, fence, Tree),
    init_horizontal_fence(X+1, W-1, Y, New_Tree).

init_vertical_fence(_, _, 0, Tree) ->
    Tree;
init_vertical_fence(X, Y, H, Tree) ->
    New_Tree = init_cell(X, Y, fence, Tree),
    init_vertical_fence(X, Y+1, H-1, New_Tree).

%% cell initialization with default values 
%% for all timers.
%%
%% Params:
%% X, Y          initial coordinates of the element
%% Type          type atom {fence, grass, rabbit, fox}
%% Position-PID  gb_trees position map to insert the new position
%%
%% Returns updated position map of type gb_trees
init_cell(X, Y, Type, Position_PID) ->
    case Type of
	fence ->
	    Pid = spawn(creatures, fence, [X, Y]),
	    addToTree({X,Y}, {Pid, Type}, Position_PID);
	grass ->
	    Pid = spawn(creatures, grass, [X, Y, 4]),
	    addToTree({X,Y}, {Pid, Type}, Position_PID);
	rabbit ->
	    Pid = spawn(creatures, rabbit, [X, Y, {5, 8, 15, 7}]),
	    addToTree({X,Y}, {Pid, Type}, Position_PID);
	fox ->
	    Pid = spawn(creatures, fox, [X, Y, {10, 15, 20, 9}]),
	    addToTree({X,Y}, {Pid, Type}, Position_PID) 
    end.

addToTree(Position, Value, Position_PID) ->
    New_Position_PID = gb_trees:insert(Position, Value, Position_PID),
    {X, Y} = Position,
    update_view(X, Y, New_Position_PID),
    New_Position_PID.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Main loop
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Main loop that keeps track of which elements
%% have not respondend yet during the current turn
%% and handles their incomming messages.
%% Iff all elements have responded, a new turn is created
%%
%% Params:
%% Turn_List     gb_sets of all the elements that have not
%%               responded during the current turn
%% Position_PID  gb_trees that maps Coordinates of an element
%%               to its pid and type-atom.
%% Turn_Cont     keeps track of the turn count
boardLoop(Turn_List, Position_PID, Turn_Count) ->
	case gb_sets:is_empty(Turn_List) of
	    true ->
		io:fwrite("Turn: ~w!~n", [Turn_Count]),
		frame! {set_time, lists:flatten(io_lib:format("Time: ~w", [Turn_Count]))},
		timer:sleep(1000),
		%create a new Turn_List using all entries in the Position_PID tree (except static fence elements)
		Values = gb_trees:values(Position_PID),
		No_Fences = [PID || {PID, Type}<-Values,Type/=fence],
		lists:foreach(fun(Pid) -> Pid! {start, self()} end, No_Fences),
      	        boardLoop(gb_sets:from_list(No_Fences), Position_PID, Turn_Count+1);
	    false ->
		%Process messages by creatures who did not yet respond during this term.
	        receive
		    {request_neighbors, {X, Y}, PID_Sender} -> 
			PID_Sender ! {neighbors, get_neighbors(X,Y, Position_PID)},
			boardLoop(Turn_List, Position_PID, Turn_Count);
		    {move, {Old_X, Old_Y}, {X, Y}, PID_Sender} ->
			move_creature(Old_X, Old_Y, X, Y, PID_Sender, Turn_List, Position_PID, Turn_Count);
		    {do_nothing, PID_Sender} ->
			New_Turn_List = gb_sets:del_element(PID_Sender, Turn_List),
			PID_Sender ! {ok, self()},
			boardLoop(New_Turn_List, Position_PID, Turn_Count);
		    {starve, X, Y, PID_Sender} ->
			starve(X, Y, PID_Sender, Turn_List, Position_PID, Turn_Count);
		    {eat, {X, Y}, {Prey_X, Prey_Y}, Prey_Type, PID_Child, PID_Sender} ->
			eat_and_procreate(X, Y, Prey_X, Prey_Y, Prey_Type, PID_Child, PID_Sender, Turn_List, Position_PID, Turn_Count);
		    {procreate, New_PID, TYPE, {X, Y}, {Child_X, Child_Y}, PID_Sender} ->
			procreate(New_PID, TYPE, X, Y, Child_X, Child_Y, PID_Sender, Turn_List, Position_PID, Turn_Count)
		end
        end. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Actions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
move_creature(Old_X, Old_Y, X, Y, PID_Sender, Turn_List, Position_PID, Turn_Count) ->
    case gb_trees:lookup({Old_X, Old_Y}, Position_PID) of
	{value, {PID_Sender, Type}} ->
	    case gb_trees:lookup({X,Y}, Position_PID) of
		none ->
		    %move to an empty spot and inform sender about success
		    New_Position_PID = gb_trees:insert({X,Y}, {PID_Sender, Type}, gb_trees:delete_any({Old_X,Old_Y}, Position_PID)),
		    New_Turn_List = gb_sets:del_element(PID_Sender, Turn_List),
		    update_view(Old_X, Old_Y, New_Position_PID),
		    update_view(X, Y, New_Position_PID),
		    PID_Sender ! {ok, self()},
		    boardLoop(New_Turn_List, New_Position_PID, Turn_Count);
		{value, _} ->
		    %Spot is already occupied.
		    PID_Sender ! {conflict, get_neighbors(Old_X,Old_Y, Position_PID)},
		    boardLoop(Turn_List, Position_PID, Turn_Count)
	    end;
	_ ->
	    %Spot is already occupied.
	    PID_Sender ! {conflict, get_neighbors(Old_X,Old_Y, Position_PID)},
	    boardLoop(Turn_List, Position_PID, Turn_Count)
   end.
		    
eat_and_procreate(X, Y, Prey_X, Prey_Y, Prey_Type, PID_Child, PID_Sender, Turn_List, Position_PID, Turn_Count) ->
    case gb_trees:lookup({Prey_X, Prey_Y}, Position_PID) of	   
        {value, {PID_Prey, Prey_Type}} ->
	    case gb_trees:get({X,Y}, Position_PID) of
		{PID_Sender, Type_Sender} ->
		    Removed_Prey_Position_PID =  gb_trees:delete_any({Prey_X,Prey_Y}, Position_PID),
		    Removed_Parent_Position_PID =  gb_trees:delete_any({X,Y}, Removed_Prey_Position_PID),
		    Moved_Parent_Position_PID = gb_trees:insert({Prey_X,Prey_Y}, {PID_Sender, Type_Sender}, Removed_Parent_Position_PID),
		    Added_Child_Position_PID = gb_trees:insert({X,Y}, {PID_Child, Type_Sender}, Moved_Parent_Position_PID),
		    New_Turn_List1 = gb_sets:del_element(PID_Sender, Turn_List), 
		    New_Turn_List2 = gb_sets:del_element(PID_Prey, New_Turn_List1),
		    update_view(X, Y, Added_Child_Position_PID),	
		    update_view(Prey_X, Prey_Y, Added_Child_Position_PID),
		    PID_Prey ! {die, self()},
		    PID_Sender ! {ok, self()},
		    boardLoop(New_Turn_List2, Added_Child_Position_PID, Turn_Count);
		_ ->
		    PID_Sender ! {conflict, get_neighbors(X,Y, Position_PID)},
		    boardLoop(Turn_List, Position_PID, Turn_Count)
	    end;
	_ ->
	    %Can't eat because Prey doesn't exist anymore (at the specified location).
	    PID_Sender ! {conflict, get_neighbors(X,Y, Position_PID)},
	    boardLoop(Turn_List, Position_PID, Turn_Count)
	end.

starve(X, Y, PID_Sender, Turn_List, Position_PID, Turn_Count) ->
    case gb_sets:is_member(PID_Sender, Turn_List) of
	true ->
	    New_Turn_List = gb_sets:del_element(PID_Sender, Turn_List),
	    New_Position_PID = gb_trees:delete_any({X,Y}, Position_PID),
	    update_view(X, Y, New_Position_PID),
	    PID_Sender ! {ok, self()},
	    boardLoop(New_Turn_List, New_Position_PID, Turn_Count);
	false -> 
	    %Can't starve because Process doesn't exist anymore..
	    boardLoop(Turn_List, Position_PID, Turn_Count)
	end.

procreate(New_PID, TYPE, X, Y, Child_X, Child_Y, PID_Sender, Turn_List, Position_PID, Turn_Count) ->
    case gb_sets:is_member(PID_Sender, Turn_List) of
	true ->
	    case gb_trees:lookup({Child_X, Child_Y}, Position_PID) of
                none ->
	            New_Position_PID = gb_trees:insert({Child_X, Child_Y}, {New_PID, TYPE}, Position_PID),
		    New_Turn_List = gb_sets:del_element(PID_Sender, Turn_List),
		    update_view(Child_X, Child_Y, New_Position_PID),	
		    PID_Sender ! {ok, self()},
		    boardLoop(New_Turn_List, New_Position_PID, Turn_Count);
		{value, _} ->
		    %Child spot is occupied
		    PID_Sender ! {conflict, get_neighbors(X,Y, Position_PID)},
		    boardLoop(Turn_List, Position_PID, Turn_Count)
	    end;
	false ->
	    %Can't procreate because sender process is not existing anymore.
	    PID_Sender ! {conflict, get_neighbors(X,Y, Position_PID)},
	    boardLoop(Turn_List, Position_PID, Turn_Count)
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Utilities
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_neighbors(X,Y, Position_PID) ->
    L1=add_neighbor_to_list(X-1, Y-1, Position_PID, []),
    L2=add_neighbor_to_list(X-1, Y, Position_PID, L1),
    L3=add_neighbor_to_list(X-1, Y+1, Position_PID, L2),    
    L4=add_neighbor_to_list(X, Y-1, Position_PID, L3),
    L5=add_neighbor_to_list(X, Y+1, Position_PID, L4),
    L6=add_neighbor_to_list(X+1, Y-1, Position_PID, L5),
    L7=add_neighbor_to_list(X+1, Y, Position_PID, L6),
    L8=add_neighbor_to_list(X+1, Y+1, Position_PID, L7),
    L8.

add_neighbor_to_list(X, Y, Position_PID, List) ->
    case gb_trees:lookup({X, Y}, Position_PID) of
	none ->
	    [{self(), none, X, Y}]++List;
	{value, {PID, Type}} ->
	    [{PID, Type, X, Y}] ++ List
    end.

update_view(X, Y, Position_PID) ->
    case gb_trees:lookup({X, Y}, Position_PID) of
	none ->
	    frame!{set_cell, X, Y, empty};
	{value, {_, Type}} ->
	    frame!{set_cell, X, Y, Type}
    end.
